﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using BReusable;
using System.Diagnostics;


//TODO: make a class to divide up a path into parts

/// <summary>
/// These extensions should be specific enough to not clash or otherwise overfill method selection
/// </summary>
public static class Extensions
{

	/// <summary>
	/// From
	/// http://davidhayden.com/blog/dave/archive/2006/11/26/IsTypeNullableTypeConverter.aspx
	/// </summary>
	/// <param name="theType"></param>
	/// <returns></returns>
	public static bool IsNullable(this Type theType)
	{
		return (theType.IsGenericType && theType.
		  GetGenericTypeDefinition().Equals
		  (typeof(Nullable<>)));
	}

	/// <summary>
	/// Alternative to using (Targ arg=new TArg()){}
	/// Using expression, instead of statement
	/// http://smellegantcode.wordpress.com/2009/02/08/a-functional-replacement-for-the-using-statement/
	/// </summary>
	/// <typeparam name="TArg"></typeparam>
	/// <typeparam name="TResult"></typeparam>
	/// <param name="arg"></param>
	/// <param name="usage"></param>
	/// <returns></returns>
	public static TResult Use<TArg, TResult>(
	  this TArg arg, Func<TArg, TResult> usage)
	  where TArg : IDisposable
	{
		try
		{
			return usage(arg);
		}
		finally
		{
			arg.Dispose();
		}
	}

    /// <summary>
    /// from: http://www.moggoly.me.uk/blog/post/Enum-description-values.aspx
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string DescriptionOrToString<T>(this T value) where T:struct 
    {

        var da = (DescriptionAttribute[])(typeof(T).GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false));

        return da.Length > 0 ? da[0].Description : value.ToString();

    }


    public static string ToStringOrEmpty<T>(this Nullable<T> data)
        where T:struct
    {
        if (data.HasValue)
            return data.Value.ToString();
        return string.Empty;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="type"></param>
    /// <param name="inherit">specifies whether to search the inheritance chain for the attribute
    /// normally false</param>
    /// <returns></returns>
    public static IEnumerable< TResult> GetTypeAttribute<TResult>(this Type type,bool inherit) where TResult:Attribute
    {
        var attribs = (TResult[]) type.GetCustomAttributes(typeof (TResult), inherit);
        return attribs;
    }
    public static TResult GetTypeAttribute<TAttribute,TResult>(this Type type, bool inherit,
        Func<IEnumerable<TAttribute>,TResult> attributeValueFunc) where TAttribute:Attribute
    {
        return attributeValueFunc(GetTypeAttribute<TAttribute>(type, inherit));
    }
    public static TResult  GetAttribute<T,TResult>(this T obj,bool inherit) where TResult:Attribute
    {
        var attribs = (TResult[]) (typeof (T).GetCustomAttributes(typeof(TResult), inherit));
        return attribs.Single();
    }
    public static TResult GetAttribute<T, TAttribute, TResult>(this T value, bool inherit, Func<TAttribute, TResult> attributeValueFunc)
        where TAttribute:Attribute
    {
        var attrib = value.GetAttribute<T,TAttribute>(inherit);
        return attributeValueFunc(attrib);
		
    }
    public static int Million(this int value)
    {
        return value * 1000000;
    }
    public static int Thousand(this int value)
    {
        return value * 1000;
    }
    /// <summary>
    /// Gets all combined items from an enum value.
    /// from: http://stackoverflow.com/questions/105372/c-how-to-enumerate-an-enum
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value">The value.</param>
    /// <returns></returns>
    public static IEnumerable<T> GetAllSelectedItems<T>(this Enum value)
    {
        int valueAsInt = Convert.ToInt32(value, CultureInfo.InvariantCulture);

        foreach (object item in Enum.GetValues(typeof(T)))
        {
            int itemAsInt = Convert.ToInt32(item, CultureInfo.InvariantCulture);

            if (itemAsInt == (valueAsInt & itemAsInt))
            {
                yield return (T)item;
            }
        }
    }

	public static bool IsEven(this int i)
	{
		return i%2 == 0;
	
	}
	public static bool IsEven(this byte b)
	{
		return b % 2 == 0;
	}

	/// <summary>
	/// Usage: mytype.IsSubclassOfRawGeneric(typeof(list<>))
	/// From BReusable
	/// </summary>
	/// <param name="generic"></param>
	/// <param name="toCheck"></param>
	/// <returns></returns>
	public static bool IsSubclassOfRawGeneric(this Type toCheck, Type generic)
	{
		while (toCheck != typeof(object))
		{
			var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
			if (generic == cur)
			{
				return true;
			}
			toCheck = toCheck.BaseType;
		}
		return false;
	}
    /// <summary>
    /// To suppress UI assert messages use:
    /// Trace.Listeners.Clear();
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="assertAgainst"></param>
    /// <param name="condition"></param>
    /// <param name="exception"></param>
    /// <returns></returns>
    public static T Assert<T>(this T assertAgainst, Func<T,bool> condition,Func<Exception> exception)
    {
        var conditionMet = condition(assertAgainst);
        if (Debugger.IsAttached)
            Debug.Assert(conditionMet);
        
            //assertAgainst.Assert(x => x != null, () => new NullReferenceException());
            if (!conditionMet)
                throw exception();
        
        return assertAgainst;
    }

	public static void AssertThenThrow(this bool condition)
	{
		Debug.Assert(condition);
		if(condition!=true)
		throw new Exception("Assertion failed");
	}

	public static T ThrowIfNull<T>(this T item ) where T : class
	{
		if (item == null) throw new NullReferenceException();
		return item;
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="c"></param>
	/// <returns></returns>
	public static int ToAscii(this char c)
	{
		return c;
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="text"></param>
	/// <returns></returns>
	public static IEnumerable<byte> ToAsciiList(this String text)
	{
		return Encoding.ASCII.GetBytes(text);
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="text"></param>
	/// <param name="e"></param>
	/// <returns></returns>
	public static IEnumerable<byte> ToEncodedValues(this String text, Encoding e)
	{
		return e.GetBytes(text);
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="sb"></param>
	/// <param name="addList"></param>
	/// <returns></returns>
	public static StringBuilder Append(this StringBuilder sb, IEnumerable<String> addList)
	{
		addList.ForEach(item => sb.Append(item));
		return sb;
	}


	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="sb"></param>
	/// <param name="addList"></param>
	/// <returns></returns>
	public static StringBuilder AppendLine(this StringBuilder sb, IEnumerable<String> addList)
	{
		addList.ForEach(item => sb.AppendLine(item));
		return sb;
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="source"></param>
	/// <returns></returns>
	public static T DirectCast<T>(this object source)
	{
		return (T)source;
	}


	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="s"></param>
	/// <returns></returns>
	public static bool IsNullOrEmpty(this String s)
	{
		return string.IsNullOrEmpty(s);
	}
    /// <summary>
    /// BReusable
    /// string.IsNullOrEmpty()==false
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static bool HasValue(this string s)
    {
        return string.IsNullOrEmpty(s) == false;
    }
    public static bool IsIgnoreCaseMatch(this string s, string comparisonText)
    {
        return s.StrComp(comparisonText, true) == 0;
    }

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="s"></param>
	/// <param name="doWhatIfFalse"></param>
	public static void IsNullOrEmpty(this String s, Action<String> doWhatIfFalse)
	{
		if (s.IsNullOrEmpty() == false)
			doWhatIfFalse(s);
	}

	/// <summary>
	/// Checks if the string is null, empty, or just whitespace (tab, returns, spaces)
	/// From BReusable
	/// </summary>
	/// <param name="text"></param>
	/// <returns></returns>
	public static bool IsNullOrWhitespace(this String text)
	{
		return text.IsNullOrEmpty() || (text.IsMatch(@"^\s+$", false));
	}

	/// <summary>
	/// Checks if the string is null, empty, or just whitespace (tab, returns, spaces)
	/// From BReusable
	/// </summary>
	/// <param name="text"></param>
	/// <param name="doWhatIfFalse">If string is not null or whitespace, do what?</param>
	public static void IsNullOrWhitespace(this String text, Action<String> doWhatIfFalse)
	{
		if (text.IsNullOrWhitespace() == false) doWhatIfFalse(text);
	}

	//Not sure if this is useful

	//public static T IsNullOrEmpty<T>(this String s, Func<String,T> DoWhatIfFalse)
	//{
	//    if (s.IsNullOrEmpty() == false)
	//        return DoWhatIfFalse(s);
	//    else
	//        return default(T);
	//}

	/// <summary>
	/// Takes the object, does a ToString and writes it out to the debug stream
	/// returns the result of the ToString
	/// From BReusable
	/// </summary>
	/// <param name="output"></param>
	/// <returns></returns>
	public static String ToStringToDebug(this object output)
	{
		var outputString = output.ToString();
		Debug.WriteLine(outputString);
		return outputString;

	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="text"></param>
	/// <param name="doWhatIfValid"></param>
	public static void IsValidDateTime(this String text, Action<DateTime> doWhatIfValid)
	{
		DateTime dateTime;
		if (DateTime.TryParse(text, out dateTime))
			doWhatIfValid(dateTime);
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="text"></param>
	/// <returns></returns>
	public static DateTime? IsValidDateTime(this String text)
	{
		DateTime dateTime;
		if (DateTime.TryParse(text, out dateTime))
			return dateTime;
		return null;
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="str1"></param>
	/// <param name="str2"></param>
	/// <param name="ignoreCase"></param>
	/// <returns></returns>
	public static int StrComp(this String str1, String str2, bool ignoreCase)
	{
		return string.Compare(str1, str2, ignoreCase);
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="ts"></param>
	/// <returns></returns>
	public static String WholePartOnly(this Stopwatch ts)
	{
		var time = ts.Elapsed.ToString();
		return Regex.Replace(time, @"\..*$", string.Empty);
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="ts"></param>
	/// <param name="itemsProcessedDuringTimer"></param>
	/// <returns></returns>
	public static String XPerSecond(this Stopwatch ts, int itemsProcessedDuringTimer)
	{
		//var time = ts.Elapsed;
		return (itemsProcessedDuringTimer
			 / (ts.ElapsedMilliseconds / 1000)).ToString();
	}


	/// <summary>
	/// Currently includes strings and DateTime properties
	/// From BReusable
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="o"></param>
	/// <returns></returns>
	public static IDictionary<String, Action<T, object>> MapPropertySetters<T>(this T o)
	{
		var properties = new Dictionary<string, Action<T, object>>(StringComparer.CurrentCultureIgnoreCase);
		System.Reflection.PropertyInfo[] s = o.GetType().GetProperties();
		for (int i = 0; i < s.GetUpperBound(0) + 1; i++)
		{
			var prop = s[i];
			if (prop.CanWrite)
			{
				if (prop.PropertyType == typeof(DateTime))
				{
					properties.Add(prop.Name, (obj, val) => prop.SetValue(obj, (DateTime)val, null));
				}
				else if (prop.PropertyType == typeof(String))
					properties.Add(prop.Name, (obj, val) => prop.SetValue(obj, ((String)val).Trim(), null));
				else if (prop.PropertyType == typeof(int))
					properties.Add(prop.Name, (obj, val) => prop.SetValue(obj, val, null));
			}
		}
		return properties;
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="o"></param>
	/// <returns></returns>
	public static System.Reflection.PropertyInfo[] ReflectProperties(this object o)
	{
		return o.GetType().GetProperties();
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="o"></param>
	/// <param name="name"></param>
	/// <returns></returns>
	public static System.Reflection.PropertyInfo ReflectProperty(this object o, String name)
	{
		return o.GetType().GetProperty(name);
	}


	/// <summary>
	/// From BReusable
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="source"></param>
	/// <param name="destination"></param>
	public static void CopyReadWriteProperties<T>(this T source, T destination)
	{
		foreach (var prop in source.ReflectProperties())
		{
			if (prop.CanRead && prop.CanWrite)
			{
				var oldValue = source.GetType().GetProperty(prop.Name).GetValue(source, null);
				destination.GetType().GetProperty(prop.Name).SetValue(destination, oldValue, null);
			}
		}
	}
    public static void CopyReadWriteProperties<T>(this T source, T destination, IEnumerable<string> blackList)
    {
        foreach (var property in source.ReflectProperties())
        {
            if (property.CanRead && property.CanWrite && (blackList == null || blackList.Contains(property.Name) == false))
            {
                var oldValue = source.GetType().GetProperty(property.Name).GetValue(source, null);
                destination.GetType().GetProperty(property.Name).SetValue(destination, oldValue, null);
            }
        }
    }
	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="o"></param>
	/// <param name="writer"></param>
	public static void Dump(this object o, System.IO.TextWriter writer)
	{
		var props = o.ReflectProperties();
		foreach (var item in props)
		{
			try
			{
				writer.WriteLine(item.Name + ": " + item.GetValue(o, null));
			}
			catch (Exception)
			{

				writer.WriteLine(item.Name + ":" + "<unknown>");
			}
		}
	}



	/// <summary>
	/// From BReusable
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="enumString"></param>
	/// <returns></returns>
	public static T? ParseEnum<T>(this String enumString) where T : struct
	{
		if (Enum.IsDefined(typeof(T), enumString))
			return (T)Enum.Parse(typeof(T), enumString);
		return new T?();
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="bytes"></param>
	/// <returns></returns>
	public static double ConvertBytesToMegabytes(this long bytes)
	{
		return (bytes / 1024f) / 1024f;
	}

	/// <summary>
	/// From BReusable
	/// </summary>
	/// <param name="kilobytes"></param>
	/// <returns></returns>
	public static double ConvertKilobytesToMegabytes(this long kilobytes)
	{
		return kilobytes / 1024f;
	}

    /// <summary>
    /// BReusable.Extensions
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="item"></param>
    /// <param name="doWhat"></param>
	public static void IfNotNull<T>(this T item, Action<T> doWhat) where T:class
	{
		if (item != null)
			doWhat(item);
	}

    /// <summary>
    /// BReusable.Extensions
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="item"></param>
    /// <param name="doWhat"></param>
    /// <returns>the value or null</returns>
	public static TResult IfNotNull<T,TResult>(this T item, Func<T,TResult> doWhat) where T:class
	{
		if(item!=null)
			return doWhat(item);
		return default(TResult);
	}
	

	#region Not Actually Extensions

	/// <summary>
	/// Safe RaiseEvent to check if there is a handler or not
	/// From BReusable
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="evnt"></param>
	public static void RaiseEvent(object sender, EventHandler evnt)
	{
		var handler = evnt;
		if (handler != null)
			handler(sender, EventArgs.Empty);
	}


	#endregion
}

